#!/usr/bin/env python
# -*- coding: UTF-8 -*-

#------------------------------------------------------------------------------
# Copyright (c) 2010 Giuseppe Buzzanca (PiDy) <giuseppebuzzanca@gmail.com>.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contributors:
#     Giuseppe Buzzanca (PiDy) <giuseppebuzzanca@gmail.com>
#         initial API and implementation
#------------------------------------------------------------------------------

from google.appengine.ext import webapp, db
from google.appengine.ext.webapp.util import run_wsgi_app
import logging
from xml.etree import ElementTree
from google.appengine.api import urlfetch
from dbclasses import Activities, Users
from activity import Activity
from postmethods import postTweet
from properties import W3XMLNS, BUZZXMLNS, PCXMLNS, \
    ACTXMLNS, GEORSSXMLNS, URL_SUBSCRIBE, LIKED_CONTENT_TEMPLATE, CRSPSTXMLNS
import sys


class Callback(webapp.RequestHandler):

    """Extend webapp.RequestHandler.
    Class Callback is mapped on /callback request.
    """

    def post(self):
        """The method called on POST request.
        Do the parsing of the Google Buzz XML feed, find the required
        parameters and update the status on twitter.
        It receives the Google Buzz XML feed provided by the pubsubhubbub hub
        in the body of the request.
        The body is passed trough a POST HTTP request mapped on the /callback url.
        """
        request = self.request
        if 'hub.mode' in request.arguments():
            self.get()
        else:
            # Only for local debugging uncomment the next row and comment
            # the following one
            #xmlFeed = open("dev-files/postSample.xml", "r").read()
            xmlFeed = self.request.body
            logging.debug(xmlFeed.replace("\n", ""))
            xmlFeed = ElementTree.fromstring(xmlFeed)
            updatedFeed = xmlFeed.find(W3XMLNS + "updated").text
            entry = xmlFeed.find(W3XMLNS + "entry")
            userID = entry.find(W3XMLNS + "author").find("{http://portablecontacts.net/ns/1.0}id").text
            logging.debug("Found UserID in main feed: " + str(userID))
            activityID = entry.find(W3XMLNS + "id").text
            # Search the actvity ID into the database
            currentActivity = Activities.get_by_key_name(activityID)
            commentContent = None
            likedContent = None
            # If test is true this is new buzz
            # Create a new entry into the Activities table
            # else search if this is a reply of a like of an another buzz
            if not currentActivity:
                a = Activities(key_name = activityID, repliesCount = 0, likedCount = 0)
                a.put()
                currentActivity = a
                logging.debug("This is a new activity, stored in DB")
            # Search and parse all the link present into the XML feed
            # If a replies link is founded it controls that the count of
            # reply is less than the number stored into the DB.
            # In this case the XML feed represent a reply to another one,
            # then the content and the ID are taken from the reply XML feed
            # It's check, also that the update date of the XML is equal to the
            # date of the last comment.
            for link in list(entry.findall(W3XMLNS + "link")):
                if link.get("rel") == "replies":
                    count = int(link.get("{http://purl.org/syndication/thread/1.0}count"))
                    logging.debug("Current count in this feed about comment: " + str(count))
                    commentUpdated = link.get("{http://purl.org/syndication/thread/1.0}updated")
                    logging.debug("Current count for this feed in DB: " + str(currentActivity.repliesCount))
                    if currentActivity.repliesCount < count and updatedFeed == commentUpdated:
                        logging.debug("This is a comment, upgrade count for this activity")
                        try:
                            currentActivity.repliesCount = count
                            db.put(currentActivity)
                            commentFeed = urlfetch.fetch(url = link.get("href"), deadline = 10)
                            logging.debug(commentFeed.content)
                            commentEntry = ElementTree.fromstring(commentFeed.content).findall(W3XMLNS + "entry").pop()
                            commentContent = commentEntry.find(W3XMLNS + "content").text
                            userID = commentEntry.find(W3XMLNS + "author").find("{http://portablecontacts.net/ns/1.0}id").text
                            logging.debug("Found UserID in comment feed: " + str(userID))
                            break
                        except:
                            logging.error("Error on fetch comment url and/or parse result")
                            logging.error(sys.exc_info())
                            return
                # If a liked link is founded it controls that the count of
                # likes is less than the number stored into the DB.
                # In this case the XML feed represent a like to another,
                # then the ID are taken from the likes XML feed and the
                # content is formatted with a template string.
                elif link.get("rel") == "http://schemas.google.com/buzz/2010#liked":
                    count = int(link.get(BUZZXMLNS + "count"))
                    logging.debug("Current count in this feed about like: " + str(count))
                    logging.debug("Current liked for this feed in DB: " + str(currentActivity.likedCount))
                    if currentActivity.likedCount < count:
                        logging.debug("This is a liked feed, upgrade count for this acitvity")
                        try:
                            currentActivity.likedCount = count
                            db.put(currentActivity)
                            likedFeed = urlfetch.fetch(url = link.get("href"), deadline = 10)
                            logging.debug(likedFeed.content)
                            likedEntry = ElementTree.fromstring(likedFeed.content).find(PCXMLNS + "entry")
                            userID = likedEntry.find(PCXMLNS + "id").text
                            logging.debug("Found UserID in liked feed: " + str(userID))
                            likedContent = LIKED_CONTENT_TEMPLATE
                            break
                        except:
                            logging.error("Error on fetch like url and/or parse result")
                            logging.error(sys.exc_info())
                            return
            if not userID:
                logging.error("Error on find user ID in Feed XML, are you sure that it is a buzz feed?")
                return
            u = Users.get_by_key_name(userID)
            if not u:
                logging.warning('No user found in DB for this feed (' + userID + ')')
                return
            if not u.active:
                logging.warning("Post to twitter temporarily disabled by administrator for this user, this update is not sent to twitter")
                return
            logging.debug("This post will be posted from this user: " + userID)
            actObject = entry.find(ACTXMLNS + "object")
            # Find the url of this buzz
            link = actObject.find(W3XMLNS + "link").get('href')
            if commentContent:
                act = Activity(commentContent, link)
            elif likedContent:
                act = Activity(likedContent + actObject.find(W3XMLNS + "content").text, link)
            else:
                act = Activity(actObject.find(W3XMLNS + "content").text, link)
                # Find the location point if present
                tmpGeoRSS = entry.find(GEORSSXMLNS + 'point')
                if tmpGeoRSS != None:
                    act.setGeoPoints(tmpGeoRSS.text.split()[0], tmpGeoRSS.text.split()[1])
                source = entry.find(CRSPSTXMLNS + "source")
                if source:
                    if source.find(W3XMLNS + "id").text.split(",")[0] == "tag:twitter.com":
                        act.setSource("tag:twitter.com")
                # Find all the attachments only if this is a new buzz
                foundAttachs = actObject.findall(BUZZXMLNS + "attachment")
                # For each attach founded insert it into a list
                # A dictionary is inserted with the keys type, title (if present) and link of the attachment
                for attach in foundAttachs:
                    type = attach.find(ACTXMLNS + 'object-type').text
                    type = type[type.rfind("/") + 1:]
                    try:
                        title = attach.find(W3XMLNS + 'title').text
                    except AttributeError:
                        title = None
                    link = attach.find(W3XMLNS + "link").get('href')
                    act.setAttach(type, title, link)
            logging.debug("Feed parsed")
            logging.debug("Content:")
            logging.debug(act.content)
            logging.debug("Original link:")
            logging.debug(act.originalLink)
            logging.debug("geo RSS point:")
            logging.debug(act.getGeoPoints())
            logging.debug("Attachments present in this post: ")
            logging.debug(act.getAttachs())
            logging.debug("Contact twitter for post update")
            # Post to twitter
            postTweet(act, u)

    def get(self):
        """Return the hub.challenge only if a hub.mode parameter is present.
        It's called from the pubsubhubbub hub for verify the callback
        both for the subscription that the unsubscription.
        If the hub.mode parameter isn't specified call the post function.
        The parameters is passed trough a GET HTTP request mapped on the /callback url.
        """
        request = self.request
        if not 'hub.mode' in request.arguments():
            self.post()
        else:
            hubChallenge = request.get('hub.challenge')
            if request.get('hub.mode') == 'subscribe':
                logging.debug("Subscribe call received from " + URL_SUBSCRIBE + " to callback, response with hub.challenge=" + hubChallenge)
                self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
                self.response.out.write(hubChallenge)
            else:
                logging.debug("Unsubscribe call received from " + URL_SUBSCRIBE + " to callback")
                self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
                self.response.out.write(hubChallenge)


application = webapp.WSGIApplication([('/callback', Callback)], debug = True)

if __name__ == "__main__":
    run_wsgi_app(application)
